﻿using System;
using System.Collections.Generic;
using BizElements.Core;

namespace BizElements.BusinessLayer
{
    #region Documentation.
    /// <summary>
    /// Configurable factory class that returns <see cref="IEntitySecurityProvider"/> singleton objects.
    /// </summary>
    /// <remarks>If not otherwise configured, <b>BizElements.BusinessLayer.EntitySecurityProvider</b> is used as
    /// a default provider for object types which don't have a dedicated/custom provider registered. This can
    /// be changed in the <b>appSettings</b> section of an application's config file:
    /// <code>
    /// &lt;add key="BizElements.BusinessLayer.SecurityProviderFactory.DefaultProviderAssembly" value="BizElements.BusinessLayer"/&gt;
    /// &lt;add key="BizElements.BusinessLayer.SecurityProviderFactory.DefaultProviderClass" value="BizElements.BusinessLayer.EntitySecurityProvider"/&gt;
    /// </code>
    /// </remarks>
    /// <seealso cref="IEntitySecurityProvider"/><seealso cref="SecurityUtil"/>
    #endregion
    public static class SecurityProviderFactory
    {
        static Dictionary<string, IEntitySecurityProvider> customProviders = new Dictionary<string, IEntitySecurityProvider>();

        /// <summary>Default security provider used for object types which don't have a dedicated/custom provider registered.</summary>
        public static readonly IEntitySecurityProvider DefaultProvider;

        static SecurityProviderFactory()
        {
            DefaultProvider = ReflectionUtil.TryCreateInstanceFromAppSettings("BizElements.BusinessLayer.SecurityProviderFactory.DefaultProviderAssembly", "BizElements.BusinessLayer.SecurityProviderFactory.DefaultProviderClass") as IEntitySecurityProvider;
            if (DefaultProvider == null)
                DefaultProvider = (IEntitySecurityProvider)ReflectionUtil.TryCreateInstance("BizElements.BusinessLayer", "BizElements.BusinessLayer.EntitySecurityProvider");

            if (DefaultProvider == null)
                throw new InvalidProgramException("Cannot create default IEntitySecurityProvider instance.");
        }

        /// <summary>Gets security provider for the specified entity type.</summary>
        /// <param name="objectType">Entity type. Typically a class or a table name.</param>
        /// <returns>Specialized provider for the specified entity type or <see cref="DefaultProvider"/> if no special provider is defined for the requested type.</returns>
        public static IEntitySecurityProvider GetProvider(string objectType)
        {
            IEntitySecurityProvider provider;
            customProviders.TryGetValue(objectType, out provider);
            return provider ?? DefaultProvider;
        }

        /// <summary>
        /// Registers dedicated <see cref="IEntitySecurityProvider"/> object to use with the specified entity type.
        /// </summary>
        /// <param name="objectType">Entity type. Typically a class or a table name.</param>
        /// <param name="provider">Custom provider which will handle access privileges for the specified type.</param>
        /// <remarks>Once a custom provider is registered for an object type it cannot be changed or unregistered.
        /// Consecutive RegisterManager method invocations for the same object type are ignored.</remarks>
        public static void RegisterProvider(string objectType, IEntitySecurityProvider provider)
        {
            if (string.IsNullOrEmpty(objectType)) 
                throw new ArgumentNullException("objectType", "Object type must be specified.");
            if (provider == null) 
                throw new ArgumentNullException("provider", "Security provider must be specifed.");

            bool alreadyRegistered = customProviders.ContainsKey(objectType);
            if (!alreadyRegistered)
            {
                lock (customProviders)
                {
                    customProviders[objectType] = provider;
                }
            }
        }
    }
}
